#ifndef MAPPED_FILE_HPP
#define MAPPED_FILE_HPP


#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <stdint.h>
#include <stdexcept>
#include <ios>
#include <iostream>
#include <iosfwd>
#include <fstream>

#include "mapped_object.h++"
// please read this ... ABSOLUTELY !
// http://www.boost.org/doc/libs/1_45_0/doc/html/interprocess/sharedmemorybetweenprocesses.html#interprocess.sharedmemorybetweenprocesses.mapped_region_object_limitations
// it describes the limitations one has when using objects on a memory mapped file.

//using boost::interprocess;



#define MAGIC "SEARCHYOURCHIPS!"
#define MAGIC_SIZE 16

struct MappedFileHeader : MemoryMappedObject {
	char magic_str[MAGIC_SIZE];
	uint32_t factor;
	void initialize(uint32_t _factor) {
		memcpy(magic_str, MAGIC, MAGIC_SIZE);
		factor = _factor;
	}
	bool check() {
		return !memcmp(magic_str, MAGIC, MAGIC_SIZE);
	}
	template <typename T> T& get_object_after() {
		return *get_pointer_after<MappedFileHeader, T>();
	}
};


using namespace boost::interprocess;
class MappedMemory {
protected:
	typedef boost::interprocess::file_mapping file_mapping;
	typedef boost::interprocess::mapped_region mapped_region;
	file_mapping file_map;
	mapped_region memory_map;
	uint32_t factor;

protected:
	MappedMemory() {}

	void open_mapping(std::string a, bool writable, const void* addr=NULL) {
		file_map = file_mapping(a.c_str(), writable ? read_write : read_only);		
		memory_map = mapped_region(file_map, writable ? read_write : read_only, 0, 0, addr);
	}
	void close_mapping() {
		memory_map = mapped_region();
		file_map = file_mapping();
	}
	void* get_mapped_memory() {
		return memory_map.get_address();
	}
	const void* get_mapped_memory() const {
		return memory_map.get_address();
	}
	virtual void* get_raw_void(ptrdiff_t index, size_t) {
		void * mmapp = get_mapped_memory();
		mmapp = (reinterpret_cast<char*>(mmapp) + index);
		return mmapp;
	}
	virtual const void* get_raw_void(ptrdiff_t index, size_t) const {
		const void * mmapp = get_mapped_memory();
		mmapp = (reinterpret_cast<const char*>(mmapp) + index);
		return mmapp;
	}
	template <typename T> T& get_raw(ptrdiff_t index) {
		T& ret = *reinterpret_cast<T*>(
				get_raw_void(index, sizeof (T)));
		return ret;
	}
	template <typename T> const T& get_raw(ptrdiff_t index) const {
		const T& ret = *reinterpret_cast<const T*>(
				get_raw_void(index, sizeof (T)));
		return ret;
	}
public:
	template <typename T> T& get(MemoryMappedPointer e) {
		return get_raw<T>(e.address * factor);
	}
	template <typename T> T& get(PointerTo<T> e) {
		return get_raw<T>(e.pointer.address * factor);
	}
	template <typename T> const T& get(MemoryMappedPointer e) const {
		return get_raw<T>(e.address * factor);
	}
	template <typename T> const T& get(PointerTo<T> e) const {
		return get_raw<T>(e.pointer.address * factor);
	}

	MappedFileHeader& get_header() {
		return get_raw<MappedFileHeader>(0);
	}
	void write_header(size_t _factor) {
		get_header().initialize(_factor);
		factor = _factor;
	}
	bool check_header() {
		if (!get_header().check())
			return false;
		factor = get_header().factor;
		return true;
	}

};

class FileMappedMemory : public MappedMemory {
protected:
	std::string filename;
	FileMappedMemory(std::string _filename) : filename(_filename) {}
};

class ReadOnlyMappedMemory : public FileMappedMemory {
public:
	ReadOnlyMappedMemory(const std::string& _filename) : FileMappedMemory(_filename) {
		open_mapping(filename, false);
		if (!check_header())
			throw std::runtime_error("no magic header found");
	}

};

class WritableMappedMemory : public FileMappedMemory {
	size_t current_size;
	size_t break_size; // for the stupid allocator.
	void create_file(size_t size) {
		close_mapping();
		std::filebuf fbuf;
		fbuf.open(filename.c_str(), std::ios_base::in | std::ios_base::out 
			| std::ios_base::trunc | std::ios_base::binary); 
			          //Set the size
		fbuf.pubseekoff(size-1, std::ios_base::beg);
		fbuf.sputc(0);
		fbuf.close();
		
		break_size = 0;
		current_size = size;
		open_mapping(filename, true);
		write_header(1);
	}
	void extend_file(size_t new_size = 0) {
		if (new_size == 0)
			new_size = current_size * 2;
		close_mapping();
		{
			std::filebuf fbuf;
			fbuf.open(filename.c_str(),
				std::ios_base::in | std::ios_base::out
				| std::ios_base::binary);
			fbuf.pubseekoff(new_size-1, std::ios_base::beg);
			fbuf.sputc(0);
		}
		current_size = new_size;
		open_mapping(filename, true);
		if (!check_header()) {
			throw std::logic_error("cannot recover previous header"
					" after extending file !");
		}
	}
	virtual void* get_raw_void(ptrdiff_t index, size_t size) {
		if (break_size < index + size) {
			break_size = index + size;
			std::cerr << filename << ":brk extended implicitly to " << break_size << std::endl;
		}
		while (break_size > current_size)
			extend_file();
		return MappedMemory::get_raw_void(index, size);
	}

public:
	// WARNNIG ! ITERATORS AND REAL POINTERS CAN BE INVALIDATED 
	// WHEN CALLING THIS FUNCTION !!!! 
	// (FIXME : this shouldn't do it. we should always allocate in the same
	// address space, but it's a lot less portable, especially on 32 bit
	// thingies.)
	template <typename T>
	PointerTo<T> allocate_space(size_t size = sizeof(T)) {
		// round to a upper multiple of factor.
		size_t round_remainder = break_size % factor;
		if (round_remainder)
			break_size += factor - round_remainder;
		PointerTo<T> ret;
		ret.pointer.address = break_size / factor;
		break_size += size;
//		std::cout << filename << ":allocated "<< size << " bytes, brk at " <<
//			break_size << std::endl;
		return ret;
	}
	WritableMappedMemory(std::string _filename) : FileMappedMemory(_filename) {
		create_file(30);
	}
};


#endif
